/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun * Microsystems, Inc. All Rights Reserved. */ package org.netbeans.modules.jarpackager; import java.util.jar.*; import java.util.zip.CRC32; import java.util.*; import java.io.*; import java.text.MessageFormat; import org.openide.filesystems.*; import org.openide.util.enum.RemoveDuplicatesEnumeration; import org.openide.execution.NbClassPath; import org.openide.util.NbBundle; import org.openide.TopManager; import org.netbeans.modules.jarpackager.util.ProgressListener; /** Utility class for creating jar packages from a set of file objects. * Common usage is create JarContent instance which describes the jar at first * and then create instance of this class, asociated with previously * created JarContent instance. Then it's possible to create the jar * by calling one of the createJar methods. * This class also supports progress notification. To be notified about * a progress, implement ProgressListener interface and asociate the * listener with this instance. * * @author Dafe Simonek */ public class JarCreater extends Object { // Attributes /** The block size for buffered reading and writing */ private static int blockSize = 65536; /** Message format for progress info */ private static MessageFormat progressInfo; /** holds the content of the jar */ JarContent jc; /** progress listeners * @associates ProgressListener*/ HashSet listeners; /** file object currently being processed */ FileObject curFo; /** Only utitlity class, no need to instantiate. */ public JarCreater (JarContent jc) { this.jc = jc; // message format for progress message if (progressInfo == null) { progressInfo = new MessageFormat( NbBundle.getBundle(PackagingPanel.class).getString("FMT_ProgressInfo") ); } } /** Writes an archive described by asociated JarContent instance to the * specified file object. * The content of jar is collected from the root file objects * inserted by JarContent.putFile(..) and JarContent.putFiles(...) calls. * The manifest file is created automatically from main attributes * and per-entry attributes. */ public void createJar (FileObject file) throws IOException { FileLock lock = file.lock(); OutputStream os = file.getOutputStream(lock); try { createJar(os, NbClassPath.toFile(file)); } finally { os.close(); lock.releaseLock(); } } /** Writes an archive described by asociated JarContent instance to the * specified output stream. Called from createJar(FileObject) */ public void createJar (OutputStream os, File targetFile) throws IOException { long now = System.currentTimeMillis(); Manifest manifest = jc.getManifest(); JarOutputStream jos = new JarOutputStream(os, manifest); try { // set write method boolean compressed = jc.isCompressed(); jos.setMethod( compressed ? JarOutputStream.DEFLATED : JarOutputStream.STORED ); if (compressed) { jos.setLevel(jc.getCompressionLevel()); } BufferedInputStream bufIs = null; curFo = null; String curName = null; JarEntry curJarEntry = null; long curSize = 0; // get the enumeration of the content Enumeration enum = jc.fullContent(); double count = jc.filteredContent().size(); long curIndex = 1; FileObjectFilter filter = jc.getFilter(); InputStream in = null; while (enum.hasMoreElements()) { curFo = (FileObject)enum.nextElement(); // add file object to the jar if filter accepts it if (filter.accept(curFo)) { // try to open input stream for current file object try { in = curFo.getInputStream(); } catch (FileNotFoundException exc) { // warn user that this file object cannot be added // and skip to next file object cannotAddFo(curFo); continue; } // create named entry curJarEntry = createEntry(curFo, compressed, targetFile); jos.putNextEntry(curJarEntry); bufIs = new BufferedInputStream(in); try { writeEntry(jos, bufIs, compressed ? curFo.getSize() : curJarEntry.getSize()); } finally { bufIs.close(); } jos.closeEntry(); // notify progress listeners fireProgressEvent( (int)(Math.round(curIndex++ / count * 100)), progressInfo.format(new Object[] { curFo.getName() } ) ); } } // write extra info entries, if present ExtraInfoProducer extraProducer = jc.getExtraProducer(); List extraInfoList = (extraProducer == null) ? null : extraProducer.extraInfo(); if (extraInfoList != null) { BufferedOutputStream bufOs = new BufferedOutputStream(jos); for (Iterator iter = extraInfoList.iterator(); iter.hasNext(); ) { ExtraInfoProducer.ExtraEntry curEntry = (ExtraInfoProducer.ExtraEntry)iter.next(); bufIs = compressed ? null : new BufferedInputStream(curEntry.createInputStream()); try { jos.putNextEntry(createEntry( curEntry.getName(), compressed, curEntry.getSize(), bufIs, targetFile )); } finally { if (bufIs != null) { bufIs.close(); } } bufIs = new BufferedInputStream(curEntry.createInputStream()); try { int ch; while((ch = bufIs.read()) != -1) { bufOs.write(ch); } bufOs.flush(); } finally { bufIs.close(); } jos.closeEntry(); } } } finally { jos.finish(); curFo = null; } //System.out.println(System.currentTimeMillis() - now); } /** @return file object currently being added to the archive. * Returns null if creation is not in progress. */ public FileObject getProcessedFileObject () { return curFo; } /* Adds new listener which will be notified about creating progress. * @param pl new listener */ public synchronized void addProgressListener (ProgressListener pl) { if (listeners == null) listeners = new HashSet(); listeners.add(pl); } /* Removes specified listener from the listener list. * @param pl listener to remove */ public synchronized void removeProgressListener (ProgressListener pl) { if (listeners == null) return; listeners.remove(pl); } /** Fires notification about creating progress. * @param pe progress event to fire off */ protected void fireProgressEvent (int percent, String description) { if (listeners == null) return; HashSet cloned; // clone listener list synchronized (this) { cloned = (HashSet)listeners.clone(); } // fire on cloned list to prevent from modifications when firing for (Iterator iter = cloned.iterator(); iter.hasNext(); ) { ((ProgressListener)iter.next()).progress(percent, description); } } /** Creates jar entry for given file object */ JarEntry createEntry (FileObject fo, boolean compressed, File targetFile) throws IOException { BufferedInputStream bufIs = null; if (!compressed) { bufIs = new BufferedInputStream(fo.getInputStream()); } try { // we must check if we are not archiving itself // and deal with this fact, but only in STORED mode if ((!compressed) && (targetFile != null) && targetFile.equals(NbClassPath.toFile(fo))) { // trying to archive itself, so create dumb empty entry JarEntry result = new JarEntry(fo.getPackageNameExt('/', '.')); result.setSize(0); result.setCompressedSize(0); result.setCrc(new CRC32().getValue()); return result; } else { return createEntry(fo.getPackageNameExt('/', '.'), compressed, fo.getSize(), bufIs, targetFile); } } finally { if (bufIs != null) { bufIs.close(); } } } /** Create jar entry from given information. If compressed is true, * size and in parameters are ignored and needn't be set */ JarEntry createEntry (String name, boolean compressed, long size, InputStream in, File targetFile) throws IOException { JarEntry result = new JarEntry(name); // we must provide header information for the entry // if STORED method is used if (!compressed) { result.setSize(size); result.setCompressedSize(size); CRC32 crc = new CRC32(); int ch; while((ch = in.read()) != -1) { crc.update(ch); } result.setCrc(crc.getValue()); } return result; } /** Writes the content of given input stream to the specified * jar output stream */ static void writeEntry (JarOutputStream jos, InputStream is, long size) throws IOException { long left = size; byte[] content = new byte[blockSize]; int readCount = 0; int curPos = 0; while (left > 0) { int leftInBlock = (left >= blockSize) ? blockSize : (int)(size % blockSize); curPos = 0; // copy a block while (leftInBlock > 0) { readCount = is.read(content, curPos, leftInBlock); jos.write(content, curPos, readCount); curPos += readCount; leftInBlock -= readCount; } left -= blockSize; } } /** Utiltity method, notifies user that given file object * cannot be added to the archive */ private static void cannotAddFo (FileObject fo) { TopManager.getDefault().getStdOut().println( MessageFormat.format( NbBundle.getBundle(JarCreater.class).getString("FMT_CannotAddFo"), new Object[] {fo} ) ); } } /* * <<Log>> * 19 Gandalf 1.18 12/7/99 David Simonek * 18 Gandalf 1.17 10/23/99 Ian Formanek NO SEMANTIC CHANGE - Sun * Microsystems copyright in file comment * 17 Gandalf 1.16 10/13/99 David Simonek various bugfixes * concerning primarily manifest * 16 Gandalf 1.15 10/13/99 David Simonek jar content now primary * file, other small changes * 15 Gandalf 1.14 10/4/99 David Simonek * 14 Gandalf 1.13 9/13/99 David Simonek bugfixes, compressed * on/off support fixed * 13 Gandalf 1.12 9/8/99 David Simonek new version of jar * packager * 12 Gandalf 1.11 8/18/99 Jesse Glick Trivial. * 11 Gandalf 1.10 8/1/99 David Simonek automatic file list * generation to the manifest added * 10 Gandalf 1.9 6/10/99 David Simonek progress indocator + * minor bugfixes.... * 9 Gandalf 1.8 6/10/99 David Simonek progress dialog now * functional * 8 Gandalf 1.7 6/9/99 David Simonek bugfixes, progress * dialog, compiling progress.. * 7 Gandalf 1.6 6/9/99 Ian Formanek ---- Package Change To * org.openide ---- * 6 Gandalf 1.5 6/5/99 David Simonek * 5 Gandalf 1.4 6/4/99 David Simonek * 4 Gandalf 1.3 6/4/99 Petr Hamernik temporary version * 3 Gandalf 1.2 6/4/99 David Simonek manifest ceration now * correctly supported * 2 Gandalf 1.1 6/3/99 David Simonek * 1 Gandalf 1.0 5/26/99 David Simonek * $ */